package main

import (
  "fmt"
  "io/ioutil"
  "os"
  "bufio"
  "strconv"
  "strings"
  "unicode/utf8"
  "path/filepath"
)

type Str   = string
type Int   = int
type Bool  = bool
type Strs  = []string
type Ints  = []int
type Estrs = []Ints
type Table = map[string]string
type Tree  = map[string]Table

func sprintf(format string, x ...interface{}) string {
  return fmt.Sprintf(format, x...)
}

func error(s string) {
  println(s)
  os.Exit(1)
}

func stdin() string {
  reader := bufio.NewReader(os.Stdin)
  line, _ := reader.ReadString('\n')
  return line
}

func ord(s string) Int {
  _c, _ := utf8.DecodeRuneInString(s)
  return int(_c)
}

func trim(s string) string {
  return strings.TrimSpace(s)
}

func split(sep, s string) []string {
  return strings.Split(s, sep)
}

func to_chars(s string) Strs {
  _runes := []rune(s)
  _strs := []string{}
  for _, _rune := range _runes {
    _strs = append(_strs, string(_rune))
  }
  return _strs
}

func to_ints(s string) Ints {
  _runes := []rune(s)
  _ints := []int{}
  for _, _rune := range _runes {
    _ints = append(_ints, int(_rune))
  }
  return _ints
}

func join(sep string, a Strs) string {
  return strings.Join(a, sep)
}

func add(a ...string) string {
  return to_str(a)
}

func to_str(a []string) string {
  return strings.Join(a, ``)
}

func exists(ns Table, key string) bool {
   _, ok := ns[key]
  return ok
}

func is_exists(table Tree, keys ...string) bool {
  key1 := keys[0]
  _, ok := table[key1]
  if len(keys) == 1 { return ok }
  key2 := keys[1]
   _, ok2 := table[key1][key2]
  return ok2
}

func is_in(s string, a []string) bool {
  for _, _s := range a {
    if _s == s { return true }
  }
  return false
}

func end_with(s, suffix string) bool {
  return strings.HasSuffix(s, suffix)
}

func start_with(s, prefix string) bool {
  return strings.HasPrefix(s, prefix)
}

func repeat(s string, count int) string {
  return strings.Repeat(s, count)
}

func index(s, substr string) int {
  return strings.Index(s, substr)
}

func ucfirst(s string) string {
  _first_char := strings.ToUpper(first_char(s))
  return _first_char + rest_str(s)
}

func first_char(s string) string {
  _char := s[0]
  return string(_char)
}

func last_char(s string) string {
  return string(s[len(s)-1])
}

func rest_str(s string) string {
  return s[1:]
}

func chop(s string) string {
  return s[0:len(s)-1]
}

func cut(s string) string {
  return s[1:len(s)-1]
}

func first(a []string) string { return a[0] }

func last(a []string) string { return a[len(a)-1] }

func rest(a []string) []string { return a[1:] }

func to_s(i int) string {
  return strconv.Itoa(i)
}

func to_i(s string) int {
  i, err := strconv.Atoi(s)
  if err != nil {
    panic(err)
  }
  return i
}

func Map(mapping func(string) string, ra []string) []string {
  var ra_map []string
  for _, x := range ra {
    ra_map = append(ra_map, mapping(x))
  }
  return ra_map
}

func to_end(s string, i int) string {
  sub_s := s[i:]
  if index(sub_s, "\n") == -1 { return sub_s }
  index := index(sub_s, "\n")
  return sub_s[0:index]
}

func aflat(array []string) (string, string) {
  return array[0], array[1]
}

func amatch(array []string) (string, []string) {
  return array[0], rest(array)
}

func basename(_file string) string {
  return filepath.Base(_file)
}

func rename_file(from_file, to_file string) {
  if file_exists(from_file) {
    os.Rename(from_file, to_file)
  }
}

func bak_file(_file string) {
  if file_exists(_file) {
    _basename := basename(_file)
    os.Rename(_file, sprintf("Bak/%s",_basename))
  }
}

func file_exists(_file string) bool {
  if _, err := os.Stat(_file); err == nil {
    return true
  }
  fmt.Printf("file: %s not exists!\n", _file)
  return false
}

func read_file(file string) string {
  str_bytes, err := ioutil.ReadFile(file)
  if err != nil {
    fmt.Printf("file %s not exists!", file)
  }
  return string(str_bytes)
}

func write_file(file string, s string) {
  x_data := []byte(s)
  err := ioutil.WriteFile(file, x_data, 0777)
  if err != nil { panic(err) }
}

func is_alpha(_c Str) Bool {
  if is_lower(_c) {
    return true
  }
  if is_upper(_c) {
    return true
  }
  if _c == `_` {
    return true
  }
  return false
}

func is_digit(_c Str) Bool {
  _int := ord(_c)
  if _int >= 48 {
    if _int <= 57 {
      return true
    }
  }
  return false
}

func is_hspace(_c Str) Bool {
  if _c == string(9) {
    return true
  }
  if _c == string(32) {
    return true
  }
  return false
}

func is_lower(_c Str) Bool {
  _int := ord(_c)
  if _int >= 97 {
    if _int <= 122 {
      return true
    }
  }
  return false
}

func is_space(_c Str) Bool {
  if is_hspace(_c) {
    return true
  }
  if is_vspace(_c) {
    return true
  }
  return false
}

func is_upper(_c Str) Bool {
  _int := ord(_c)
  if _int >= 65 {
    if _int <= 90 {
      return true
    }
  }
  return false
}

func is_vspace(_c Str) Bool {
  if _c == string(10) {
    return true
  }
  if _c == string(13) {
    return true
  }
  return false
}

func is_words(_c Str) Bool {
  if is_digit(_c) {
    return true
  }
  if is_alpha(_c) {
    return true
  }
  return false
}

func is_xdigit(_c Str) Bool {
  if is_digit(_c) {
    return true
  }
  _int := ord(_c)
  if _int >= 65 {
    if _int <= 70 {
      return true
    }
  }
  if _int >= 97 {
    if _int <= 102 {
      return true
    }
  }
  return false
}

func is_oper(_str Str) Bool {
  _ochars := `-+=><!~`
  for _, _char := range to_chars(_str) {
    if index(_ochars, _char) == -1 {
      return false
    }
  }
  return true
}

func is_str(_str Str) Bool {
  _char := first_char(_str)
  return ord(_char) > 5
}

func is_estr(_str Str) Bool {
  return first_char(_str) == string(3)
}

func is_atom(_atom Str) Bool {
  if first_char(_atom) == string(3) {
    if string(_atom[1]) == string(5) {
      return true
    }
  }
  return false
}

func is_blank(_str Str) Bool {
  return _str == add(string(3), string(4))
}

func estr_atom(_atom Str) Str {
  if is_estr(_atom) {
    return _atom
  }
  return add(string(5), _atom)
}

func estr(_args ...Str) Str {
  return estr_strs(_args)
}

func estr_strs(_array Strs) Str {
  _estr := to_str(Map(estr_atom, _array))
  return add(string(3), _estr, string(4))
}

func from_ejson(_json Str) Str {
  _chars := []string{}
  _mode := 0
  for _, _ch := range to_chars(_json) {
    if _mode == 0 {
      switch _ch {
        case `[`: {
          _chars = append(_chars, string(3))
        }
        case `]`: {
          _chars = append(_chars, string(4))
        }
        case `"`: {
          _chars = append(_chars, string(5))
          _mode = 1
        }
      }
    } else if _mode == 1 {
      switch _ch {
        case `"`: {
          _mode = 0
        }
        case string(92): {
          _mode = 2
        }
        default: {
          _chars = append(_chars, _ch)
        }
      }
    } else {
      _mode = 1
      switch _ch {
        case `t`: {
          _chars = append(_chars, string(9))
        }
        case `n`: {
          _chars = append(_chars, string(10))
        }
        case `r`: {
          _chars = append(_chars, string(13))
        }
        default: {
          _chars = append(_chars, _ch)
        }
      }
    }
  }
  return to_str(_chars)
}

func to_ejson(_estr Str) Str {
  if is_str(_estr) {
    return _estr
  }
  _chars := []string{}
  _mode := 0
  for _, _ch := range to_chars(_estr) {
    if _mode == 0 {
      switch _ch {
        case string(3): {
          _chars = append(_chars, `[`)
        }
        case string(5): {
          _chars = append(_chars, `"`)
          _mode = 1
        }
        case string(4): {
          _chars = append(_chars, `]`)
          _mode = 2
        }
      }
    } else if _mode == 1 {
      switch _ch {
        case string(3): {
          _chars = append(_chars, `",[`)
          _mode = 0
        }
        case string(5): {
          _chars = append(_chars, `","`)
        }
        case string(4): {
          _chars = append(_chars, `"]`)
          _mode = 2
        }
        default: {
          _chars = append(_chars, char_to_ejson(_ch))
        }
      }
    } else {
      switch _ch {
        case string(3): {
          _chars = append(_chars, `,[`)
          _mode = 0
        }
        case string(5): {
          _chars = append(_chars, `,"`)
          _mode = 1
        }
        case string(4): {
          _chars = append(_chars, `]`)
        }
      }
    }
  }
  return to_str(_chars)
}

func char_to_ejson(_ch Str) Str {
  switch _ch {
    case string(9): {
      return `\t`
    }
    case string(10): {
      return `\n`
    }
    case string(13): {
      return `\r`
    }
    case string(92): {
      return `\\`
    }
    case `"`: {
      return `\"`
    }
    default: {
      return _ch
    }
  }
}

func atoms(_estr Str) Strs {
  _estrs := []string{}
  _chars := []string{}
  _depth := 0
  _mode := false
  for _, _ch := range to_chars(_estr) {
    if _depth == 0 {
      if _ch == string(3) {
        _depth += 1
      }
    } else if _depth == 1 {
      switch _ch {
        case string(3): {
          _depth += 1
          if _mode {
            _estrs = append(_estrs, to_str(_chars))
            _chars = []string{}
          }
          _mode = true
          _chars = append(_chars, _ch)
        }
        case string(5): {
          if _mode {
            _estrs = append(_estrs, to_str(_chars))
            _chars = []string{}
          }
          _mode = true
        }
        case string(4): {
          if _mode {
            _estrs = append(_estrs, to_str(_chars))
          }
        }
        default: {
          if _mode {
            _chars = append(_chars, _ch)
          }
        }
      }
    } else {
      if _ch == string(3) {
        _depth += 1
      }
      if _ch == string(4) {
        _depth -= 1
      }
      _chars = append(_chars, _ch)
    }
  }
  return _estrs
}

func flat(_estr Str) (Str,Str) {
  if is_str(_estr) {
    println(sprintf("flat str: %s", _estr))
  }
  _atoms := atoms(_estr)
  return _atoms[0], _atoms[1]
}

func match(_estr Str) (Str,Str) {
  _atoms := atoms(_estr)
  return _atoms[0], estr_strs(rest(_atoms))
}

func name(_estr Str) Str {
  _chars := []string{}
  _str := _estr[2:]
  for _, _char := range to_chars(_str) {
    if ord(_char) > 5 {
      _chars = append(_chars, _char)
    } else {
      return to_str(_chars)
    }
  }
  return to_str(_chars)
}

func value(_estr Str) Str {
  _atoms := atoms(_estr)
  return _atoms[1]
}

func elen(_estr Str) Int {
  _atoms := atoms(_estr)
  return len(_atoms)
}

func efirst(_estr Str) Str {
  return first(atoms(_estr))
}

func epush(_estr Str,_elem Str) Str {
  return add(chop(_estr), _elem, string(4))
}

func eappend(_a_one Str,_a_two Str) Str {
  return add(chop(_a_one), rest_str(_a_two))
}

func eunshift(_elem Str,_array Str) Str {
  return add(string(3), _elem, rest_str(_array))
}

func is_atom_name(_atom Str,_name Str) Bool {
  if is_atom(_atom) {
    return name(_atom) == _name
  }
  return false
}

func clean_ast(_ast Str) Str {
  if is_atom(_ast) {
    return clean_atom(_ast)
  }
  _clean_atoms := []string{}
  for _, _atom := range atoms(_ast) {
    _clean_atoms = append(_clean_atoms, clean_atom(_atom))
  }
  return estr_strs(_clean_atoms)
}

func clean_atom(_atom Str) Str {
  _name, _value := flat(_atom)
  if is_str(_value) {
    return estr(_name, _value)
  }
  if is_blank(_value) {
    return estr(_name, _value)
  }
  if is_atom(_value) {
    return estr(_name, clean_atom(_value))
  }
  return estr(_name, clean_ast(_value))
}

func get_spp_ast() Str {
  return `[["door",["Rules",[["Rept",["+",["Branch",[["Rept",["+",["Cclass","s"]]],["Rtoken","_comm"],["Ntoken","Spec"]]]]],["Blank"," "],["Assert","$"]]]],["_comm",["Rules",[["Char","#"],["Till",["Assert","$$"]]]]],["Spec",["Rules",[["Ntoken","Token"],["Blank"," "],["Str","->"],["Blank"," "],["Ctoken","rule"]]]],["rule",["Rules",[["Rept",["+",["Branch",[["Ntoken","Blank"],["Ntoken","Branch"],["Ctoken","atom"]]]]],["Branch",[["Cclass","v"],["Assert","$"]]]]]],["Blank",["Rept",["+",["Cclass","h"]]]],["atom",["Branch",[["Ntoken","Group"],["Ntoken","Token"],["Ntoken","Str"],["Ntoken","String"],["Ntoken","Kstr"],["Ntoken","Cclass"],["Ntoken","Char"],["Ntoken","Chclass"],["Ntoken","Assert"],["Ntoken","Any"],["Ntoken","Rept"],["Ntoken","Till"]]]],["Branch",["Rules",[["Char","|"],["Blank"," "],["Rept",["+",["Branch",[["Rept",["+",["Cclass","s"]]],["Rtoken","_comm"],["Ctoken","atom"]]]]],["Blank"," "],["Char","|"]]]],["Group",["Rules",[["Char","{"],["Blank"," "],["Rept",["+",["Branch",[["Rept",["+",["Cclass","s"]]],["Rtoken","_comm"],["Ntoken","Branch"],["Ctoken","atom"]]]]],["Blank"," "],["Char","}"]]]],["Token",["Rept",["+",["Chclass",[["Cclass","a"],["Cchar","-"]]]]]],["Kstr",["Rules",[["Char",":"],["Rept",["+",["Chclass",[["Cclass","a"],["Cchar","-"]]]]]]]],["Str",["Rules",[["Char","'"],["Rept",["+",["Branch",[["Rept",["+",["Nclass",[["Cchar","\\"],["Cchar","'"]]]]],["Group",[["Char","\\"],["Any","."]]]]]]],["Char","'"]]]],["String",["Rules",[["Char","\""],["Rept",["+",["Branch",[["Rept",["+",["Nclass",[["Cchar","\\"],["Cchar","\""]]]]],["Group",[["Char","\\"],["Any","."]]]]]]],["Char","\""]]]],["Cclass",["Rules",[["Char","\\"],["Chclass",[["Cchar","a"],["Cchar","d"],["Cchar","h"],["Cchar","l"],["Cchar","n"],["Cchar","r"],["Cchar","s"],["Cchar","t"],["Cchar","u"],["Cchar","v"],["Cchar","w"],["Cchar","x"]]]]]],["Char",["Rules",[["Char","\\"],["Any","."]]]],["Chclass",["Rules",[["Char","["],["Blank"," "],["Rept",["?",["Ntoken","Flip"]]],["Rept",["+",["Branch",[["Cclass","s"],["Ntoken","Cclass"],["Ntoken","Char"],["Ntoken","Range"],["Ntoken","Cchar"]]]]],["Blank"," "],["Char","]"]]]],["Flip",["Char","^"]],["Range",["Rules",[["Cclass","w"],["Char","-"],["Cclass","w"]]]],["Cchar",["Nclass",[["Cclass","s"],["Cchar","]"],["Cchar","#"],["Cchar","\\"]]]],["Assert",["Branch",[["Str","^^"],["Str","$$"],["Char","^"],["Char","$"]]]],["Any",["Char","."]],["Rept",["Chclass",[["Cchar","?"],["Cchar","*"],["Cchar","+"]]]],["Till",["Char","~"]]]`
}

func get_spp_grammar() Str {
  return `
    door    -> |\s+ _comm Spec|+ $
    _comm   -> '#'~$$
    Spec    -> Token '->' rule
    rule    -> | Blank Branch atom |+| \v $ |
    Blank   -> \h+
    atom    -> | Group Token Str String Kstr Cclass
                 Char Chclass Assert Any Rept Till |
    Branch  -> '|' | \s+ _comm atom |+ '|'
    Group   -> '{' | \s+ _comm Branch atom |+ '}'
    Token   -> [\a\-]+
    Kstr    -> ':'[\a\-]+
    Str     -> \'| [^\\']+ {\\.} |+\'
    String  -> \"| [^\\"]+ {\\.} |+\"
    Cclass  -> \\[adhlnrstuvwx]
    Char    -> \\.
    Chclass -> \[ Flip?|\s Cclass Char Range Cchar|+ \]
    Flip    -> '^'
    Range   -> \w'-'\w
    Cchar   -> [^ \s \] \# \\ ]
    Assert  -> | '^^' '$$' '^' '$' |
    Any     -> '.'
    Rept    -> [?*+]
    Till    -> '~'
    `
}

func opt_spp_ast(_ast Strs) Strs {
  return Map(opt_spp_atom, _ast)
}

func opt_spp_atom(_atom Str) Str {
  _name, _value := flat(_atom)
  switch _name {
    case `Spec`: {
      return opt_spp_spec(_value)
    }
    case `Rule`: {
      return opt_spp_spec(_value)
    }
    case `Group`: {
      return opt_spp_group(_value)
    }
    case `Branch`: {
      return opt_spp_branch(_value)
    }
    case `Cclass`: {
      return opt_spp_cclass(_value)
    }
    case `Char`: {
      return opt_spp_char(_value)
    }
    case `Str`: {
      return opt_spp_str(_value)
    }
    case `String`: {
      return opt_spp_str(_value)
    }
    case `Kstr`: {
      return opt_spp_kstr(_value)
    }
    case `Chclass`: {
      return opt_spp_chclass(_value)
    }
    case `Token`: {
      return opt_spp_token(_value)
    }
    case `Rept`: {
      return estr(`rept`, _value)
    }
    default: {
      return _atom
    }
  }
}

func opt_spp_spec(_atoms Str) Str {
  _token, _rules := match(_atoms)
  _name := value(_token)
  _opt_rules := opt_spp_rules(_rules)
  return estr(_name, _opt_rules)
}

func opt_spp_rules(_atoms Str) Str {
  _opt_atoms := opt_spp_atoms(_atoms)
  if len(_opt_atoms) == 1 {
    return first(_opt_atoms)
  }
  return estr(`Rules`, estr_strs(_opt_atoms))
}

func opt_spp_group(_atoms Str) Str {
  _opt_atoms := opt_spp_atoms(_atoms)
  if len(_opt_atoms) == 1 {
    return first(_opt_atoms)
  }
  return estr(`Group`, estr_strs(_opt_atoms))
}

func opt_spp_branch(_atoms Str) Str {
  _opt_atoms := opt_spp_atoms(_atoms)
  if len(_opt_atoms) == 1 {
    return first(_opt_atoms)
  }
  return estr(`Branch`, estr_strs(_opt_atoms))
}

func opt_spp_atoms(_atoms Str) Strs {
  return gather_spp_rept(gather_spp_till(opt_spp_ast(atoms(_atoms))))
}

func opt_spp_str(_string Str) Str {
  _chars := []string{}
  _mode := 0
  _value := cut(_string)
  for _, _char := range to_chars(_value) {
    if _mode == 0 {
      if _char == string(92) {
        _mode = 1
      } else {
        _chars = append(_chars, _char)
      }
    } else {
      _mode = 0
      switch _char {
        case `n`: {
          _chars = append(_chars, string(10))
        }
        case `r`: {
          _chars = append(_chars, string(13))
        }
        case `t`: {
          _chars = append(_chars, string(9))
        }
        default: {
          _chars = append(_chars, last_char(_char))
        }
      }
    }
  }
  _str := to_str(_chars)
  if len(_str) == 1 {
    return estr(`Char`, _str)
  }
  return estr(`Str`, _str)
}

func opt_spp_kstr(_kstr Str) Str {
  _str := rest_str(_kstr)
  if len(_str) == 1 {
    return estr(`Char`, _str)
  }
  return estr(`Str`, _str)
}

func opt_spp_cclass(_cclass Str) Str {
  return estr(`Cclass`, last_char(_cclass))
}

func opt_spp_char(_char Str) Str {
  return estr(`Char`, last_char(_char))
}

func opt_spp_chclass(_nodes Str) Str {
  _atoms := []string{}
  _flip := 0
  for _, _node := range atoms(_nodes) {
    _name, _value := flat(_node)
    if _name == `Flip` {
      _flip = 1
    } else {
      _atom := opt_spp_catom(_name, _value)
      _atoms = append(_atoms, _atom)
    }
  }
  if _flip == 0 {
    return estr(`Chclass`, estr_strs(_atoms))
  }
  return estr(`Nclass`, estr_strs(_atoms))
}

func opt_spp_catom(_name Str,_value Str) Str {
  switch _name {
    case `Cclass`: {
      return opt_spp_cclass(_value)
    }
    case `Range`: {
      return opt_spp_range(_value)
    }
    case `Char`: {
      return opt_spp_cchar(_value)
    }
    default: {
      return estr(`Cchar`, _value)
    }
  }
}

func opt_spp_cchar(_char Str) Str {
  return estr(`Cchar`, last_char(_char))
}

func opt_spp_range(_atom Str) Str {
  return estr(`Range`, estr_strs(split(`-`, _atom)))
}

func gather_spp_till(_atoms Strs) Strs {
  _opt_atoms := []string{}
  _flag := 0
  for _, _atom := range _atoms {
    if _flag == 0 {
      if is_atom_name(_atom, `Till`) {
        _flag = 1
      } else {
        _opt_atoms = append(_opt_atoms, _atom)
      }
    } else {
      if !is_atom_name(_atom, `Till`) {
        _opt_atoms = append(_opt_atoms, estr(`Till`, _atom))
        _flag = 0
      }
    }
  }
  return _opt_atoms
}

func gather_spp_rept(_atoms Strs) Strs {
  _opt_atoms := []string{}
  _flag := 0
  _cache := ""
  for _, _atom := range _atoms {
    if _flag == 0 {
      if !is_atom_name(_atom, `rept`) {
        _cache = _atom
        _flag = 1
      }
    } else {
      if is_atom_name(_atom, `rept`) {
        _rept := value(_atom)
        _cache = estr(`Rept`, estr(_rept, _cache))
        _opt_atoms = append(_opt_atoms, _cache)
        _flag = 0
      } else {
        _opt_atoms = append(_opt_atoms, _cache)
        _cache = _atom
      }
    }
  }
  if _flag == 1 {
    _opt_atoms = append(_opt_atoms, _cache)
  }
  return _opt_atoms
}

func opt_spp_token(_name Str) Str {
  _char := first_char(_name)
  if is_upper(_char) {
    return estr(`Ntoken`, _name)
  }
  if is_lower(_char) {
    return estr(`Ctoken`, _name)
  }
  return estr(`Rtoken`, _name)
}


type Cursor struct {
  text Str
  table Table
  pos Int
  line Int
  depth Int
  capture Strs
}

func new_cursor(_str Str,_table Table) *Cursor {
  _text := add(_str, string(0))
  return &Cursor{
   text: _text,
   table: _table,
   pos: 0,
   line: 1,
   depth: 0,
   capture: []string{},
  }
}

func match_table(_table Table,_text Str) Strs {
  return match_door(_table, _text, `door`)
}

func match_door(_table Table,_text Str,_door Str) Strs {
  _rule := _table[_door]
  _c := new_cursor(_text, _table)
  _match := match_spp_rule(_c, _rule)
  if is_no(_match) {
    fail_report(_c)
  }
  return _c.capture
}

func is_ok(_str Str) Bool {
  return _str == string(1)
}

func is_no(_str Str) Bool {
  return _str == string(2)
}

func get_char(_c *Cursor) Str {
  _text := _c.text
  _pos := _c.pos
  return string(_text[_pos])
}

func to_next(_c *Cursor)  {
  _c.pos += 1
  if get_char(_c) == string(10) {
    _c.line += 1
  }
}

func cache(_c *Cursor) Int {
  return _c.pos
}

func reset_cache(_c *Cursor,_cache Int)  {
  _c.pos = _cache
}

func fail_report(_c *Cursor)  {
  _text := _c.text
  _pos := _c.pos
  _line := to_s(_c.line)
  if len(_text) <= _pos {
    error("code less ) ] }")
  }
  _line_str := to_end(_text, _pos)
  error(sprintf("line: %s Stop match:\n%s\n^", _line,_line_str))
}

func match_spp_rule(_c *Cursor,_rule Str) Str {
  _name, _value := flat(_rule)
  switch _name {
    case `Rules`: {
      return match_spp_rules(_c, _value)
    }
    case `Group`: {
      return match_spp_rules(_c, _value)
    }
    case `Branch`: {
      return match_spp_branch(_c, _value)
    }
    case `Blank`: {
      return match_spp_blank(_c)
    }
    case `Rept`: {
      return match_spp_rept(_c, _value)
    }
    case `Cclass`: {
      return match_spp_cclass(_c, _value)
    }
    case `Chclass`: {
      return match_spp_chclass(_c, _value)
    }
    case `Nclass`: {
      return match_spp_nclass(_c, _value)
    }
    case `Str`: {
      return match_spp_str(_c, _value)
    }
    case `Char`: {
      return match_spp_char(_c, _value)
    }
    case `Assert`: {
      return match_spp_assert(_c, _value)
    }
    case `Till`: {
      return match_spp_till(_c, _value)
    }
    case `Rtoken`: {
      return match_spp_rtoken(_c, _value)
    }
    case `Ctoken`: {
      return match_spp_ctoken(_c, _value)
    }
    case `Ntoken`: {
      return match_spp_ntoken(_c, _value)
    }
    case `Any`: {
      return match_spp_any(_c)
    }
    default: {
      return string(2)
    }
  }
}

func match_spp_any(_c *Cursor) Str {
  _char := get_char(_c)
  if _char == string(0) {
    return string(2)
  }
  to_next(_c)
  return _char
}

func match_spp_assert(_c *Cursor,_assert Str) Str {
  switch _assert {
    case `$`: {
      if get_char(_c) == string(0) {
        return string(1)
      }
      return string(2)
    }
    case `$$`: {
      if get_char(_c) == string(10) {
        return string(1)
      }
      if get_char(_c) == string(0) {
        return string(1)
      }
      return string(2)
    }
    default: {
      error(sprintf("unknown assert: |%s|", _assert))
    }
  }
  return string(2)
}

func match_spp_blank(_c *Cursor) Str {
  for is_space(get_char(_c)) {
    _c.pos += 1
  }
  return string(1)
}

func match_spp_rules(_c *Cursor,_rules Str) Str {
  _gather := string(1)
  for _, _rule := range atoms(_rules) {
    _match := match_spp_rule(_c, _rule)
    if is_no(_match) {
      return string(2)
    }
    _gather = gather_spp_match(_gather, _match)
  }
  return _gather
}

func match_spp_branch(_c *Cursor,_branch Str) Str {
  _cache := cache(_c)
  for _, _rule := range atoms(_branch) {
    _match := match_spp_rule(_c, _rule)
    if !is_no(_match) {
      return _match
    }
    reset_cache(_c, _cache)
  }
  return string(2)
}

func match_spp_ntoken(_c *Cursor,_name Str) Str {
  _table := _c.table
  _rule := _table[_name]
  _c.depth += 1
  _match := match_spp_rule(_c, _rule)
  _c.depth -= 1
  if is_ok(_match) {
    return _match
  }
  if is_no(_match) {
    return _match
  }
  _line := to_s(_c.line)
  _return := name_spp_match(_name, _match, _line)
  if _c.depth == 0 {
    _c.capture = append(_c.capture, _return)
    return string(1)
  }
  return _return
}

func match_spp_ctoken(_c *Cursor,_name Str) Str {
  _table := _c.table
  _rule := _table[_name]
  return match_spp_rule(_c, _rule)
}

func match_spp_rtoken(_c *Cursor,_name Str) Str {
  _ns := _c.table
  _rule := _ns[_name]
  _match := match_spp_rule(_c, _rule)
  if is_no(_match) {
    return string(2)
  }
  return string(1)
}

func match_spp_till(_c *Cursor,_rule Str) Str {
  _buf := []string{}
  _len := len(_c.text)
  for _c.pos < _len {
    _char := get_char(_c)
    _cache := cache(_c)
    _match := match_spp_rule(_c, _rule)
    if !is_no(_match) {
      _gather := to_str(_buf)
      return gather_spp_match(_gather, _match)
    }
    _buf = append(_buf, _char)
    reset_cache(_c, _cache)
    to_next(_c)
  }
  return string(2)
}

func match_spp_rept(_c *Cursor,_rule Str) Str {
  _gather := string(1)
  _time := 0
  _rept, _atom := flat(_rule)
  _min, _max := get_rept_time(_rept)
  for _time != _max {
    _cache := cache(_c)
    _match := match_spp_rule(_c, _atom)
    if is_no(_match) {
      if _time < _min {
        return string(2)
      }
      reset_cache(_c, _cache)
      return _gather
    }
    _time += 1
    _gather = gather_spp_match(_gather, _match)
  }
  return _gather
}

func get_rept_time(_rept Str) (Int,Int) {
  switch _rept {
    case `?`: {
      return 0, 1
    }
    case `*`: {
      return 0, -1
    }
    case `+`: {
      return 1, -1
    }
    default: {
      return 0, 1
    }
  }
}

func match_spp_str(_c *Cursor,_str Str) Str {
  for _, _char := range to_chars(_str) {
    if _char != get_char(_c) {
      return string(2)
    }
    to_next(_c)
  }
  return _str
}

func match_spp_char(_c *Cursor,_char Str) Str {
  if _char != get_char(_c) {
    return string(2)
  }
  to_next(_c)
  return _char
}

func match_spp_chclass(_c *Cursor,_atoms Str) Str {
  _char := get_char(_c)
  for _, _atom := range atoms(_atoms) {
    if is_match_spp_catom(_atom, _char) {
      to_next(_c)
      return _char
    }
  }
  return string(2)
}

func match_spp_nclass(_c *Cursor,_atoms Str) Str {
  _char := get_char(_c)
  if _char == string(0) {
    return string(2)
  }
  for _, _atom := range atoms(_atoms) {
    if is_match_spp_catom(_atom, _char) {
      return string(2)
    }
  }
  to_next(_c)
  return _char
}

func is_match_spp_catom(_atom Str,_char Str) Bool {
  _name, _value := flat(_atom)
  switch _name {
    case `Range`: {
      return is_match_spp_range(_value, _char)
    }
    case `Cclass`: {
      return is_match_spp_cclass(_value, _char)
    }
    case `Cchar`: {
      return _value == _char
    }
    case `Char`: {
      return _value == _char
    }
    default: {
      error(sprintf("unknown spp catom: |%s|", _name))
    }
  }
  return false
}

func match_spp_cclass(_c *Cursor,_cclass Str) Str {
  _char := get_char(_c)
  if _char == string(0) {
    return string(2)
  }
  if is_match_spp_cclass(_cclass, _char) {
    to_next(_c)
    return _char
  }
  return string(2)
}

func is_match_spp_cclass(_cchar Str,_char Str) Bool {
  switch _cchar {
    case `a`: {
      return is_alpha(_char)
    }
    case `d`: {
      return is_digit(_char)
    }
    case `h`: {
      return is_hspace(_char)
    }
    case `l`: {
      return is_lower(_char)
    }
    case `n`: {
      return _char == string(10)
    }
    case `r`: {
      return _char == string(13)
    }
    case `s`: {
      return is_space(_char)
    }
    case `t`: {
      return _char == string(9)
    }
    case `u`: {
      return is_upper(_char)
    }
    case `v`: {
      return is_vspace(_char)
    }
    case `w`: {
      return is_words(_char)
    }
    case `x`: {
      return is_xdigit(_char)
    }
    default: {
      return false
    }
  }
}

func is_match_spp_range(_range Str,_char Str) Bool {
  _from, _to := flat(_range)
  if _from <= _char {
    if _char <= _to {
      return true
    }
  }
  return false
}

func name_spp_match(_name Str,_match Str,_line Str) Str {
  if is_atom(_match) {
    return estr(_name, estr(_match), _line)
  }
  return estr(_name, _match, _line)
}

func gather_spp_match(_gather Str,_match Str) Str {
  if is_ok(_match) {
    return _gather
  }
  if is_ok(_gather) {
    return _match
  }
  if is_str(_match) {
    if is_str(_gather) {
      return add(_gather, _match)
    }
    return _gather
  }
  if is_str(_gather) {
    return _match
  }
  if is_atom(_gather) {
    if is_atom(_match) {
      return estr(_gather, _match)
    }
    return eunshift(_gather, _match)
  }
  if is_atom(_match) {
    return epush(_gather, _match)
  }
  return eappend(_gather, _match)
}

func lint_spp_ast(_ast Strs)  {
  _table := Table{}
  _values := []string{}
  for _, _atom := range _ast {
    _name, _value := flat(_atom)
    if exists(_table, _name) {
      error(sprintf("repeat define rule: |%s|", _name))
    } else {
      _table[_name] = `define`
      _values = append(_values, _value)
    }
  }
  if !exists(_table, `door`) {
    error("Spp Ast not exist rule name: |door|")
  }
  for _, _rule := range _values {
    lint_spp_rule(_table, _rule)
  }
  for _name, _ := range _table {
    if _name != `door` {
      _value := _table[_name]
      if _value == `define` {
        error(sprintf("not used rule: |%s|", _name))
      }
    }
  }
}

func lint_spp_rule(_t Table,_rule Str)  {
  _name, _atoms := flat(_rule)
  if !is_in(_name, []string{`Any`,`Str`,`Char`,`Cclass`,`Assert`,`Chclass`,`Nclass`,`Blank`}) {
    switch _name {
      case `Ctoken`: {
        lint_spp_token(_t, _atoms)
      }
      case `Ntoken`: {
        lint_spp_token(_t, _atoms)
      }
      case `Rtoken`: {
        lint_spp_token(_t, _atoms)
      }
      case `Till`: {
        lint_spp_rule(_t, _atoms)
      }
      case `Rept`: {
        lint_spp_rept(_t, _atoms)
      }
      case `Branch`: {
        lint_spp_atoms(_t, _atoms)
      }
      case `Group`: {
        lint_spp_atoms(_t, _atoms)
      }
      case `Rules`: {
        lint_spp_atoms(_t, _atoms)
      }
      default: {
        error(sprintf("miss rule |%s| lint method", _name))
      }
    }
  }
}

func lint_spp_rept(_t Table,_atoms Str)  {
  _value := value(_atoms)
  lint_spp_rule(_t, _value)
}

func lint_spp_atoms(_t Table,_atoms Str)  {
  for _, _atom := range atoms(_atoms) {
    lint_spp_rule(_t, _atom)
  }
}

func lint_spp_token(_table Table,_name Str)  {
  if exists(_table, _name) {
    _table[_name] = `ok`
  } else {
    error(sprintf("not exists rule: |%s|", _name))
  }
}

func get_spp_table() Table {
  _ejson := get_spp_ast()
  _ast := from_ejson(_ejson)
  return ast_to_table(atoms(_ast))
}

func ast_to_table(_ast Strs) Table {
  _table := Table{}
  for _, _spec := range _ast {
    _name, _rule := flat(_spec)
    _table[_name] = _rule
  }
  return _table
}

func grammar_to_ast(_table Table,_grammar Str) Strs {
  _match := match_table(_table, _grammar)
  return opt_spp_ast(_match)
}

func rule_repl()  {
  _table := get_spp_table()
  println(`This is Spp REPL, type enter to exit.`)
  for true {
    print(`>> `)
    _line := stdin()
    _line = trim(_line)
    if _line != "" {
      _match := match_door(_table, _line, `rule`)
      println(to_ejson(clean_ast(first(_match))))
    }
  }
}

func spp_repl()  {
  _table := get_spp_table()
  println(`This is Spp REPL, type enter to exit.`)
  for true {
    print(`>> `)
    _line := stdin()
    _line = trim(_line)
    if _line != "" {
      _match := match_table(_table, _line)
      _ast := opt_spp_ast(_match)
      _clean_ast := clean_ast(first(_ast))
      println(to_ejson(_clean_ast))
    }
  }
}

func update_spp_ast()  {
  _grammar := get_spp_grammar()
  _table := get_spp_table()
  _ast := grammar_to_ast(_table, _grammar)
  lint_spp_ast(_ast)
  _json := to_ejson(clean_ast(estr_strs(_ast)))
  _package := `(module Spp.SppAst)`
  _fn := "(fn (get-spp-ast) (return '''"
  _code := add(_package, _fn, _json, "'''))")
  _ast_file := `SppAst.my`
  write_file(_ast_file, _code)
  println(sprintf("update ok! write file %s", _ast_file))
}

func lint_spp_file(_file Str)  {
  _grammar := read_file(_file)
  _table := get_spp_table()
  _ast := grammar_to_ast(_table, _grammar)
  lint_spp_ast(_ast)
}

func get_spp_parser(_grammar_file Str) Table {
  _grammar := read_file(_grammar_file)
  _table := get_spp_table()
  _ast := grammar_to_ast(_table, _grammar)
  lint_spp_ast(_ast)
  return ast_to_table(_ast)
}

func parse_with_table(_table Table,_text_file Str) Str {
  _text := read_file(_text_file)
  _match := match_table(_table, _text)
  _clean_ast := clean_ast(estr_strs(_match))
  return to_ejson(_clean_ast)
}

func parse_grammar(_grammar_file Str,_text_file Str)  {
  _parser := get_spp_parser(_grammar_file)
  println(parse_with_table(_parser, _text_file))
}

func GetArgs(_args Strs)  {
  _usage := `
    Repl to debug Spp rule:
    > go run Spp.go -rule

    Repl to debug Spp grammar:
    > go run spp.go -spp

    Lint grammar:
    > go run spp.go -lintspp grammar.file

    parse text with spp grammar to Json:
    > go run spp.go -parse grammar.file text.file
    `
  _nums := len(_args)
  if _nums == 1 {
    rule_repl()
  } else if _nums == 2 {
    _flag := _args[1]
    switch _flag {
      case `-rule`: {
        rule_repl()
      }
      case `-spp`: {
        spp_repl()
      }
      case `-help`: {
        println(_usage)
      }
      case `-update`: {
        update_spp_ast()
      }
      default: {
        println(_usage)
      }
    }
  } else if _nums == 3 {
    _flag := _args[1]
    _file := _args[2]
    switch _flag {
      case `-lint`: {
        lint_spp_file(_file)
      }
      default: {
        println(_usage)
      }
    }
  } else {
    _flag := _args[1]
    _file := _args[2]
    _tfile := _args[3]
    if _flag == `-parse` {
      parse_grammar(_file, _tfile)
    } else {
      println(_usage)
    }
  }
}

func main() {
  GetArgs(os.Args)
}